import type { CustomContext, CustomState } from '..'
import type { Context, RequestOptions, ResponseOptions } from './context'
import type { IErrHookArg, IHookArg, Method } from './router'
import path from 'node:path'
import { cwd } from 'node:process'
import compose from './compose'
import { ERROR_CODE } from './constant'
import { context, request, response } from './context'
import Router from './router'
import { isAsyncFunction } from './utils'

type ConfigMiddleware<StateT = CustomState, CustomT = CustomContext> = Array<
  [Middleware<StateT, CustomT>]
>
type Middleware<
  StateT = CustomState,
  CustomT = CustomContext,
> = Context<StateT, CustomT>

export interface EasyOptions {
  /**
   * 默认路由前缀
   */
  prefix?: string
  debug?: boolean
  baseDir: string
  createDoc?: boolean
  autoRouter?: boolean
  defaultMethod?: Method
  controllerDir?: string
  middleware?: ConfigMiddleware
  extendCtx?: (ctx: Context) => void
  filterController?: (fileName: string, route: string) => string | undefined
}
interface DefaultReq extends RequestOptions {
}
interface DefaultRes extends ResponseOptions {
}
type HookName = 'onRequest' | 'onResponse' | 'onError'
type EventName = 'request' | 'response' | 'error'

class Easy extends Router {
  config: EasyOptions
  controllerDir: string
  // hooks:
  onRequest: any[]
  onResponse: any[]
  onError: any[]
  // hooks
  context: Context<CustomState, CustomContext>
  request: any
  response: any
  middleware: any
  constructor(config: EasyOptions) {
    super()
    this.config = config || {}

    const { baseDir = cwd(), autoRouter = true, prefix = '', defaultMethod = 'GET', createDoc = true, controllerDir = 'service', extendCtx } = this.config

    this.controllerDir = path.resolve(baseDir, controllerDir)

    this.middleware = []

    this.onRequest = [] // 请求入口钩子
    this.onResponse = [] // 响应发送后钩子
    this.onError = [] // 错误处理钩子

    this.context = Object.create(context)
    this.request = Object.create(request)
    this.response = Object.create(response)
    this.context.config = config
    extendCtx && extendCtx(this.context)

    autoRouter && this.autoRouter({ defaultMethod, prefix, controllerDir: this.controllerDir, test: config.filterController, createDoc })
    Promise.resolve().then(() => {
      this.use(this.routerMiddleware())
    })
    this.serve = this.serve.bind(this)
  }

  on<T extends EventName>(hookName: T, hookFunction: T extends 'error' ? IErrHookArg : IHookArg) {
    const _hookName = `on${hookName.replace(/^[a-z]/, c => c.toUpperCase())}` as HookName
    if (this[_hookName]) {
      hookFunction._async = isAsyncFunction(hookFunction)
      this[_hookName].push(hookFunction)
    }
    else {
      throw new Error(`Unknown hook: ${hookName}`)
    }
  }

  use(fn: { (ctx: any, next: any): void }) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
    this.middleware.push(fn)
    return this
  }

  createContext(req: DefaultReq, res: DefaultRes) {
    const context = Object.create(this.context)
    context.request = Object.create(this.request)
    context.response = Object.create(this.response)
    context.app = this

    context.req = context.request.req = req
    context.res = context.response.res = res || {}

    context.query = context.request.query
    context.params = context.request.params
    context.body = context.request.body

    context.state = {}
    return context
  }

  serve(req: DefaultReq, res: DefaultRes) {
    const ctx = this.createContext(req, res)
    const fn = compose(this.middleware)

    return this._runHooks('onRequest', ctx)
      .then(() => fn(ctx))
      .then(() => this._runHooks('onResponse', ctx))
      .catch(async (err) => {
        let statusCode = err.code || err.status
        // ENOENT support
        if (err.code === 'ENOENT') statusCode = 404
        if (typeof statusCode !== 'number') statusCode = 500
        ctx.response.status = statusCode
        ctx.response.body = this.failed(err, statusCode)
        try {
          await this._runHooks('onError', err, ctx)
        }
        catch (error: any) {
          ctx.response.body = this.failed(error, statusCode)
        }
        return ctx
      })
  }

  private failed(err: any, code: number) {
    // 创建一个返回对象 ret，包含错误代码和错误信息
    const ret = {
      code: code || err?.code || ERROR_CODE,
      message: err?.message || err,
    }
    if (this.config.debug === true) {
      ; (ret as any).stack = err?.stack || ''
    }
    return ret
  }

  runHook(hookName: string, hook: any, args: any) {
    if (hook._async) return hook(...args, () => { })
    return new Promise((resolve, reject) => {
      const next = (err: any) => (err ? reject(err) : resolve(hookName === 'onResponse' ? args[0] : args))
      hook(...args, next)
    })
  }

  // 执行指定钩子队列
  private _runHooks(hookName: HookName, ...args: any[]): Promise<any> {
    const hooks = this[hookName]
    const hooksLen = hooks.length
    if (!hooksLen) return Promise.resolve(args[0])
    let promise = Promise.resolve()
    for (let i = 0; i < hooksLen; i++) {
      promise = promise.then(() => this.runHook(hookName, hooks[i], args))
    }
    return promise
  }
}

export default Easy
